package com.leetroll.app.utils;

import com.leetroll.app.dto.Token;
import com.leetroll.common.config.jwt.Audience;
import com.leetroll.common.config.jwt.JwtTokenUtil;
import com.leetroll.common.config.security.SecurityUser;
import com.leetroll.common.global.BaseSysConf;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;

/**
 * JWT工具类
 */
@Component
public class AppJwtTokenUtil {

    private static final Logger log = LoggerFactory.getLogger(JwtTokenUtil.class);

    private final Audience audience;

    @Autowired
    public AppJwtTokenUtil(Audience audience) {
        this.audience = audience;
    }

    /**
     * 解析jwt
     */
    public Claims parseJWT(String token) {
        String base64Secret = audience.getBase64Secret();
        try {
            return Jwts.parser()
                    .setSigningKey(DatatypeConverter.parseBase64Binary(base64Secret))
                    .parseClaimsJws(token).getBody();
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 创建Token
     */
    public Token createToken(String name, Long id) {
        String clientId = audience.getClientId();
        String issuer = audience.getName();
        String base64Security = audience.getBase64Secret();
        //生成accessToken
        String accessToken = createJWT(name, id, clientId, issuer, audience.getExpiresSecond() * 1000, base64Security);
        //生成refreshToken
        String refreshToken = createJWT(name, id, clientId, issuer, audience.getRefreshSecond() * 1000, base64Security);
        return new Token(accessToken, refreshToken);
    }

    /**
     * 构建jwt
     *
     * @param userName       账户名
     * @param audience       代表这个Jwt的接受对象
     * @param issuer         代表这个Jwt的签发主题
     * @param TTLMillis      jwt有效时间
     * @param base64Security 加密方式
     */
    public String createJWT(String userName, Long userId,
                            String audience, String issuer, long TTLMillis, String base64Security) {
        // HS256是一种对称算法, 双方之间仅共享一个 密钥
        // 由于使用相同的密钥生成签名和验证签名, 因此必须注意确保密钥不被泄密
        // 也可以改成RS256: 非对称加密算法，使用私钥进行加密，使用公钥来验证Token的有效性
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        //生成签名密钥
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
        //添加构成JWT的参数
        JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
                .claim(BaseSysConf.USER_ID, userId)//自定义参数
                .claim(BaseSysConf.CREATE_TIME, now)//自定义参数
                .setSubject(userName)
                .setIssuer(issuer)//签发主题
                .setAudience(audience)//接受对象
                .signWith(signatureAlgorithm, signingKey);//签名
        //添加Token过期时间
        if (TTLMillis >= 0) {
            long expMillis = nowMillis + TTLMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp).setNotBefore(now);
        }
        //生成JWT
        return builder.compact();
    }

    /**
     * 判断token是否已过期
     */
    public boolean isExpiration(String token) {
        if (parseJWT(token) == null) {
            return true;
        } else {
            return parseJWT(token).getExpiration().before(new Date());
        }
    }

    /**
     * 效验token
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        SecurityUser SecurityUser = (SecurityUser) userDetails;
        final String username = getUsername(token);
        final boolean expiration = isExpiration(token);
        return (
                username.equals(SecurityUser.getUsername())
                        && !expiration);
    }

    /**
     * 从token中获取用户名
     */
    public String getUsername(String token) {
        return parseJWT(token).getSubject();
    }

    /**
     * 从token中获取用户UID
     */
    public Long getUserId(String token) {
        if (parseJWT(token) == null) return null;
        return parseJWT(token).get(BaseSysConf.USER_ID, Long.class);
    }

    /**
     * 从token中获取用户UID
     */
    public Long getUserId(HttpServletRequest request) {
        String accessToken = request.getHeader("Authorization");
        if (parseJWT(accessToken) == null) return null;
        return getUserId(accessToken);
    }

    /**
     * 从token中获取audience
     */
    public String getAudience(String token) {
        return parseJWT(token).getAudience();
    }

    /**
     * 从token中获取issuer
     */
    public String getIssuer(String token) {
        return parseJWT(token).getIssuer();
    }

    /**
     * 从token中获取过期时间
     */
    public Date getExpiration(String token) {
        return parseJWT(token).getExpiration();
    }

    /**
     * token是否可以更新
     */
    public Boolean canTokenBeRefreshed(String token) {
        return !isExpiration(token);
    }

    /**
     * 更新token
     *
     * @return accessToken 返回更新后的token，需要客户端进行更新
     */
    public String refreshToken(String refreshToken, long TTLMillis) {
        String accessToken;
        String base64Secret = audience.getBase64Secret();
        try {
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);
            // 生成签名密钥
            byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Secret);
            Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

            final Claims claims = parseJWT(refreshToken);
            claims.put("creatDate", new Date());
            JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
                    .setClaims(claims)
                    .setSubject(getUsername(refreshToken))
                    .setIssuer(getIssuer(refreshToken))
                    .setAudience(getAudience(refreshToken))
                    .signWith(signatureAlgorithm, signingKey);
            //添加Token过期时间
            if (TTLMillis >= 0) {
                long expMillis = nowMillis + TTLMillis;
                Date exp = new Date(expMillis);
                builder.setExpiration(exp).setNotBefore(now);
            }
            accessToken = builder.compact();
        } catch (Exception e) {
            accessToken = null;
        }
        log.info("刷新后的token: {}", accessToken);
        return accessToken;
    }

}
